#include "trackerrequest.h"
#include <QUrl>
#include <QtDebug>
#include <QCryptographicHash>
#include <QErrorMessage>
#include "constants.h"
#include "bencodeparser.h"
#include "torrentpeerdata.h"
#include "mainwindow.h"

//C++ requires initialisation of static variables in such a way
QByteArray TrackerRequest::peerId = "";

TrackerRequest::TrackerRequest(MetaInfo info, QObject *parent) :
        QObject(parent), httpClient(* (new QHttp(this)) ), torrentInfo(info),
                                                infoHash("")
{
    generatePeerId();
    connect(&httpClient, SIGNAL(done(bool)), this, SLOT(httpResponseReceived(bool)));
}

void TrackerRequest::start()
{

    QString announceUrl = torrentInfo.announceUrl();
    bool foundSupportedTracker = isTrackerSupported(announceUrl);
    if(!foundSupportedTracker)
    {
        QStringList trackers = torrentInfo.announceList();
        foreach(const QString& trackerUrl, trackers)
        {
            if(isTrackerSupported(trackerUrl))
            {
                announceUrl = trackerUrl;
                foundSupportedTracker = true;
                break;
            }
        }
    }
    if(!foundSupportedTracker)
    {
        emit requestError("Torrent tracker: " + announceUrl +  " is not supported");
    }
    qDebug() << "Tracker URL is: " << announceUrl;

    QUrl url(announceUrl);

    //get infohash
    infoHash = QCryptographicHash::hash(torrentInfo.infoValue(), QCryptographicHash::Sha1);
    QString percentEncodedHash;
    for (int i =0; i < infoHash.size(); i++)
    {
       percentEncodedHash += '%' ;
       QString hex = QString::number(infoHash[i], 16).right(2);
       //fill with 0 on the beggining, if there is only one digit in hex number
       hex = hex.rightJustified(2, '0');
       percentEncodedHash.append(hex);
    }
    QByteArray request;
    //format request
    request += url.path().toLatin1();
    request += "?info_hash=" + percentEncodedHash;



    request += "&peer_id=" + peerId;
    request += "&port=" + QByteArray::number(LISTENING_PORT_BEGIN);
    request += "&compact=1";
    request += "&uploaded=0";
    request += "&downloaded=0";
    request += "&left=" + QByteArray::number(torrentInfo.totalSize());
    request +="&event=started";

    //send http request
   QString trackerAddr = url.host();
   qDebug() << "tracker Address: ";   
   qDebug() << trackerAddr;
   qDebug() << "port: " + QString::number(url.port());
   qDebug() << "request: ";
   qDebug() << request;

    httpClient.setHost(url.host(), url.port() == -1 ? 80 : url.port());
    if (!url.userName().isEmpty())
        httpClient.setUser(url.userName(), url.password());
    QString requestAsString(request);
    httpClient.get(requestAsString);
    GlobalLogger::getInstance()->log("Sending request to tracker: http://" + url.host() + "/" + request);

}

void TrackerRequest::generatePeerId()
{
    //peerId is static, should be the same for all tracker requests
    //so generate it only once
    if (peerId.isEmpty()) {
        //generating the peer id in a way desribed here:
        //http://wiki.theory.org/index.php/BitTorrentSpecification#peer_id
        //for my client: client ID = PP (Peer-to-Peer project), version: 0001
        peerId += "-PP0001-";
        //append 12 random digits to get valid, 20-byte peerId
        for (int i=0; i < 12; i++)
        {
            peerId+= QByteArray::number(qrand() % 10);
        }
    }
}
void TrackerRequest::httpResponseReceived(bool error)
{
    qDebug() << "printing HTTP response: ";
     QByteArray httpResponse = httpClient.readAll();
    qDebug() << httpResponse;
    if (error) {
        QHttp::Error err =  httpClient.error();
        QString reason = "Tracker request: error " + QString::number(err);
        emit requestError(reason);
         httpClient.abort();
    }
    else {

        QString responseAsString(httpResponse);
        qDebug() << responseAsString;
        processTrackerResponse(httpResponse);
        emit requestCompleted(responseAsString);
    }

}
void TrackerRequest::processTrackerResponse(const QByteArray& response)
{
    BencodeParser parser;
    if (!parser.parse(response)) {
        qWarning("An error occured while parsing tracker's response: %s",
                 qPrintable(parser.errorString()));
        httpClient.abort();
        return;
    }
    int pieceLength = torrentInfo.singleFile().pieceLength;

    if(!pieceLength)
    {
        emit requestError("Ivalid .torrent file - piece length not specified");
        return;
    }
    int piecesCount =  (int) ( torrentInfo.totalSize() / pieceLength)
                       + ( (torrentInfo.totalSize() % pieceLength) ? 1: 0 ) ;    
    qDebug() << "TrackerRequest::processTrackerResponse: piecesCount: "
            << QString::number(piecesCount) <<", piece length: "
            << pieceLength <<", total size: " << torrentInfo.totalSize();

    QMap<QByteArray, QVariant> dict = parser.dictionary();
    if (dict.contains("peers")) {       
        QVariant peersFromResponse = dict.value("peers");
        //peers list in tracker's response can be written as either
        //dictionary or binary string
        QList<TorrentPeerData> peersData;
        if (peersFromResponse.type() == QVariant::List) {
            //dictionary model
            QList<QVariant> peerTmp = peersFromResponse.toList();
            for (int i = 0; i < peerTmp.size(); ++i) {

                QMap<QByteArray, QVariant> peer = qVariantValue<QMap<QByteArray, QVariant> >(peerTmp.at(i));

                QHostAddress ip;
                ip.setAddress(QString::fromUtf8(peer.value("ip").toByteArray()));
                quint16 port =  peer.value("port").toInt();
                QString peerId = QString::fromUtf8(peer.value("peer id").toByteArray());
                TorrentPeerData peerData(ip, port, peerId, piecesCount);
                peersData.append(peerData);
            }

        } else {
            //binary model
            QByteArray peerTmp = peersFromResponse.toByteArray();
            for (int i = 0; i < peerTmp.size(); i += 6) {
                QString peerId = "";    //no peer Id is provided in this case
                uchar *data = (uchar *)peerTmp.constData() + i;
                quint16 port = (int(data[4]) << 8) + data[5];
                uint ipAddressAsInt = 0;
                ipAddressAsInt += uint(data[0]) << 24;
                ipAddressAsInt += uint(data[1]) << 16;
                ipAddressAsInt += uint(data[2]) << 8;
                ipAddressAsInt += uint(data[3]);
                QHostAddress ip;
                ip.setAddress(ipAddressAsInt);
                qDebug() << "adding peer with IP address: " + ip.toString();

                TorrentPeerData peerData(ip, port, peerId, piecesCount);

                peersData.append(peerData);
            }
        }
        GlobalLogger::getInstance()->log("Received tracker response: list of "
                                         + QString::number(peersData.count())
                                         + " peers received");
        emit peersListReceived(peersData);
    }
}
QByteArray TrackerRequest::getInfoHash()
{
    return infoHash;
}
QByteArray& TrackerRequest::getPeerId()
{
    return peerId;
}
MetaInfo& TrackerRequest::getTorrentInfo()
{
    return torrentInfo;
}
bool TrackerRequest::isTrackerSupported(const QString &trackerAddress)
{
    //udp trackers are not supported
    //piratebay tracker is blocked in AGH network?
    if(trackerAddress.startsWith("udp")
        || trackerAddress.contains("piratebay", Qt::CaseInsensitive)
        || trackerAddress.contains("p2p.idg.pl", Qt::CaseInsensitive) )
    {
        return false;
    }
    return true;
}
